﻿Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Globalization
Imports System.Runtime.CompilerServices
Imports System.Timers
Imports System.Workflow.ComponentModel
Imports System.Workflow.Runtime.Tracking

Namespace System.Workflow.Runtime
    Friend Class TrackingListenerFactory
        ' Methods
        Friend Sub New()
        End Sub

        Private Sub Cleanup(ByVal sender As Object, ByVal e As ElapsedEventArgs)
            Dim list As New List([Of] Guid)
            If ((Not Me._listeners Is Nothing) OrElse (Me._listeners.Count > 0)) Then
                SyncLock Me._listenerLock
                    Dim pair As KeyValuePair([Of] Guid, WeakReference)
                    For Each pair In Me._listeners
                        If (pair.Value.Target Is Nothing) Then
                            list.Add(pair.Key)
                        End If
                    Next
                    If (list.Count > 0) Then
                        Dim guid As Guid
                        For Each guid In list
                            Me._listeners.Remove(guid)
                        Next
                    End If
                End SyncLock
            End If
            SyncLock Me
                If (Not Me._timer Is Nothing) Then
                    Me._timer.Start
                End If
            End SyncLock
        End Sub

        Private Function GetChannels(ByVal schedule As Activity, ByVal exec As WorkflowExecutor, ByVal instanceID As Guid, ByVal workflowType As Type, ByRef broker As TrackingListenerBroker) As List([Of] TrackingChannelWrapper)
            If (Me._services Is Nothing) Then
                Return Nothing
            End If
            Dim flag As Boolean = False
            If (broker Is Nothing) Then
                broker = New TrackingListenerBroker
                flag = True
            End If
            Dim list As New List([Of] TrackingChannelWrapper)
            Dim callPath As List([Of] String) = Nothing
            Dim empty As Guid = Guid.Empty
            Dim context As Guid = Me.GetContext(exec.RootActivity)
            Dim callerContextGuid As Guid = Guid.Empty
            Dim callerParentContextGuid As Guid = Guid.Empty
            Dim trackingCallingState As TrackingCallingState = exec.TrackingCallingState
            Dim broker1 As TrackingListenerBroker = DirectCast(exec.RootActivity.GetValue(WorkflowExecutor.TrackingListenerBrokerProperty), TrackingListenerBroker)
            Dim collection As IList([Of] String) = If((Not trackingCallingState Is Nothing), trackingCallingState.CallerActivityPathProxy, Nothing)
            If ((Not collection Is Nothing) AndAlso (collection.Count > 0)) Then
                callPath = New List([Of] String)(collection)
                empty = trackingCallingState.CallerWorkflowInstanceId
                callerContextGuid = trackingCallingState.CallerContextGuid
                callerParentContextGuid = trackingCallingState.CallerParentContextGuid
            End If
            Dim parameters As New TrackingParameters(instanceID, workflowType, exec.WorkflowDefinition, callPath, empty, context, callerContextGuid, callerParentContextGuid)
            Dim i As Integer
            For i = 0 To Me._services.Count - 1
                Dim trackingChannel As TrackingChannel = Nothing
                Dim trackingServiceType As Type = Me._services.Item(i).GetType
                Dim profile As RTTrackingProfile = Nothing
                If flag Then
                    profile = Me._profileManager.GetProfile(Me._services.Item(i), schedule)
                    If (profile Is Nothing) Then
                        [Continue] For
                    End If
                    broker.AddService(trackingServiceType, profile.Version)
                Else
                    If Not broker.ContainsService(trackingServiceType) Then
                        [Continue] For
                    End If
                    If broker.IsProfileInstance(trackingServiceType) Then
                        profile = Me._profileManager.GetProfile(Me._services.Item(i), schedule, instanceID)
                        If (profile Is Nothing) Then
                            Throw New InvalidOperationException((ExecutionStringManager.MissingProfileForService & trackingServiceType.ToString))
                        End If
                        profile.IsPrivate = True
                    Else
                        Dim version As Version
                        If Not broker.TryGetProfileVersionId(trackingServiceType, version) Then
                            [Continue] For
                        End If
                        profile = Me._profileManager.GetProfile(Me._services.Item(i), schedule, version)
                        If (profile Is Nothing) Then
                            Throw New InvalidOperationException((ExecutionStringManager.MissingProfileForService & trackingServiceType.ToString & ExecutionStringManager.MissingProfileForVersion & version.ToString))
                        End If
                        If broker.IsProfilePrivate(trackingServiceType) Then
                            profile = profile.Clone
                            profile.IsPrivate = True
                        End If
                    End If
                End If
                trackingChannel = Me._services.Item(i).GetTrackingChannel(parameters)
                If (trackingChannel Is Nothing) Then
                    Throw New InvalidOperationException(ExecutionStringManager.NullChannel)
                End If
                list.Add(New TrackingChannelWrapper(trackingChannel, Me._services.Item(i).GetType, workflowType, profile))
            Next i
            Return list
        End Function

        Friend Function GetContext(ByVal activity As Activity) As Guid
            Return DirectCast(ContextActivityUtils.ContextActivity(activity).GetValue(Activity.ActivityExecutionContextInfoProperty), ActivityExecutionContextInfo).ContextGuid
        End Function

        Private Function GetListener(ByVal sked As Activity, ByVal skedExec As WorkflowExecutor, ByVal broker As TrackingListenerBroker) As TrackingListener
            If ((sked Is Nothing) OrElse (skedExec Is Nothing)) Then
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NullParameters)
                Return Nothing
            End If
            If ((Not Me._services Is Nothing) AndAlso (Me._services.Count > 0)) Then
                Dim load As Boolean = (Not Nothing Is broker)
                Dim channels As List([Of] TrackingChannelWrapper) = Me.GetChannels(sked, skedExec, skedExec.InstanceId, sked.GetType, (broker))
                If ((Not channels Is Nothing) AndAlso (channels.Count <> 0)) Then
                    Return New TrackingListener(Me, sked, skedExec, channels, broker, load)
                End If
            End If
            Return Nothing
        End Function

        Private Function GetListenerFromWRCache(ByVal instanceId As Guid) As TrackingListener
            Dim reference As WeakReference = Nothing
            Dim target As TrackingListener = Nothing
            SyncLock Me._listenerLock
                If Not Me._listeners.TryGetValue(instanceId, reference) Then
                    Throw New InvalidOperationException(String.Format(CultureInfo.InvariantCulture, ExecutionStringManager.ListenerNotInCache, New Object() { instanceId }))
                End If
                target = [TryCast](reference.Target,TrackingListener)
                If (target Is Nothing) Then
                    Throw New ObjectDisposedException(String.Format(CultureInfo.InvariantCulture, ExecutionStringManager.ListenerNotInCacheDisposed, New Object() { instanceId }))
                End If
            End SyncLock
            Return target
        End Function

        Friend Function GetTrackingListener(ByVal sked As Activity, ByVal skedExec As WorkflowExecutor) As TrackingListener
            If Not Me._initialized Then
                Me.Initialize(skedExec.WorkflowRuntime)
            End If
            Return Me.GetListener(sked, skedExec, Nothing)
        End Function

        Friend Function GetTrackingListener(ByVal sked As Activity, ByVal skedExec As WorkflowExecutor, ByVal broker As TrackingListenerBroker) As TrackingListener
            If Not Me._initialized Then
                Me.Initialize(skedExec.WorkflowRuntime)
            End If
            If (broker Is Nothing) Then
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NullTrackingBroker)
                Return Nothing
            End If
            Return Me.GetListener(sked, skedExec, broker)
        End Function

        Friend Sub Initialize(ByVal runtime As WorkflowRuntime)
            SyncLock Me
                Me._services = runtime.TrackingServices
                Me._profileManager.Initialize(runtime)
                AddHandler runtime.WorkflowExecutorInitializing, New EventHandler([Of] WorkflowExecutorInitializingEventArgs)(AddressOf Me.WorkflowExecutorInitializing)
                Me._timer = New Timer
                Me._timer.Interval = Me._interval
                Me._timer.AutoReset = False
                AddHandler Me._timer.Elapsed, New ElapsedEventHandler(AddressOf Me.Cleanup)
                Me._timer.Start
                Me._initialized = True
            End SyncLock
        End Sub

        Friend Sub ReloadProfiles(ByVal exec As WorkflowExecutor)
            [Using] New ServiceEnvironment(exec.RootActivity)
                [Using] exec.ExecutorLock.Enter
                    If Not exec.IsInstanceValid Then
                        Throw New InvalidOperationException(ExecutionStringManager.WorkflowNotValid)
                    End If
                    Dim flag As Boolean = exec.Suspend(ExecutionStringManager.TrackingProfileUpdate)
                    Try 
                        Me.GetListenerFromWRCache(exec.InstanceId).ReloadProfiles(exec, exec.InstanceId)
                    Finally
                        If flag Then
                            exec.Resume
                        End If
                    End Try
                End [Using]
            End [Using]
        End Sub

        Friend Sub ReloadProfiles(ByVal exec As WorkflowExecutor, ByVal instanceId As Guid, ByRef broker As TrackingListenerBroker, ByRef channels As List([Of] TrackingChannelWrapper))
            Dim workflowType As Type = exec.WorkflowDefinition.GetType
            Dim service As TrackingService
            For Each service In Me._services
                Dim profile As TrackingProfile = Nothing
                Dim wrapper As TrackingChannelWrapper = Nothing
                If service.TryReloadProfile(workflowType, instanceId, profile) Then
                    Dim flag As Boolean = False
                    Dim index As Integer = 0
                    Do While (index < channels.Count)
                        If (service.GetType Is channels.Item(index).TrackingServiceType) Then
                            wrapper = channels.Item(index)
                            flag = True
                            Exit Do
                        End If
                        index += 1
                    Loop
                    If (profile Is Nothing) Then
                        If flag Then
                            broker.RemoveService(wrapper.TrackingServiceType)
                            channels.RemoveAt(index)
                        End If
                        [Continue] For
                    End If
                    Dim profile2 As New RTTrackingProfile(profile, exec.WorkflowDefinition, workflowType) { _
                        .IsPrivate = True _
                    }
                    If Not flag Then
                        Dim callPath As List([Of] String) = Nothing
                        Dim empty As Guid = Guid.Empty
                        Dim trackingCallingState As TrackingCallingState = exec.TrackingCallingState
                        Dim collection As IList([Of] String) = Nothing
                        Dim context As Guid = Me.GetContext(exec.RootActivity)
                        Dim callerContextGuid As Guid = Guid.Empty
                        Dim callerParentContextGuid As Guid = Guid.Empty
                        If (Not trackingCallingState Is Nothing) Then
                            collection = trackingCallingState.CallerActivityPathProxy
                            If ((Not collection Is Nothing) AndAlso (collection.Count > 0)) Then
                                callPath = New List([Of] String)(collection)
                                empty = trackingCallingState.CallerWorkflowInstanceId
                                callerContextGuid = trackingCallingState.CallerContextGuid
                                callerParentContextGuid = trackingCallingState.CallerParentContextGuid
                            End If
                        End If
                        Dim parameters As New TrackingParameters(instanceId, workflowType, exec.WorkflowDefinition, callPath, empty, context, callerContextGuid, callerParentContextGuid)
                        Dim item As New TrackingChannelWrapper(service.GetTrackingChannel(parameters), service.GetType, workflowType, profile2)
                        channels.Add(item)
                        Dim type As Type = service.GetType
                        broker.AddService(type, profile2.Version)
                        broker.MakeProfileInstance(type)
                        [Continue] For
                    End If
                    wrapper.SetTrackingProfile(profile2)
                    broker.MakeProfileInstance(wrapper.TrackingServiceType)
                End If
            Next
        End Sub

        Friend Sub Uninitialize(ByVal runtime As WorkflowRuntime)
            SyncLock Me
                Me._profileManager.Uninitialize
                RemoveHandler runtime.WorkflowExecutorInitializing, New EventHandler([Of] WorkflowExecutorInitializingEventArgs)(AddressOf Me.WorkflowExecutorInitializing)
                RemoveHandler Me._timer.Elapsed, New ElapsedEventHandler(AddressOf Me.Cleanup)
                Me._timer.Stop
                Me._services = Nothing
                Me._initialized = False
                Me._timer.Dispose
                Me._timer = Nothing
            End SyncLock
        End Sub

        Private Sub WorkflowExecutionEvent(ByVal sender As Object, ByVal e As WorkflowExecutionEventArgs)
            Select Case e.EventType
                Case WorkflowEventInternal.Terminated, WorkflowEventInternal.Aborted, WorkflowEventInternal.Completed
                    Dim executor As WorkflowExecutor = DirectCast(sender, WorkflowExecutor)
                    SyncLock Me._listenerLock
                        Me._listeners.Remove(executor.ID)
                    End SyncLock
                    Exit Select
                Case WorkflowEventInternal.Aborting
                    Exit Select
                Case Else
                    Return
            End Select
        End Sub

        Private Sub WorkflowExecutorInitializing(ByVal sender As Object, ByVal e As WorkflowExecutorInitializingEventArgs)
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            If (e Is Nothing) Then
                Throw New ArgumentNullException("e")
            End If
            If Not GetType(WorkflowExecutor).IsInstanceOfType(sender) Then
                Throw New ArgumentException("sender")
            End If
            Dim skedExec As WorkflowExecutor = DirectCast(sender, WorkflowExecutor)
            AddHandler skedExec.WorkflowExecutionEvent, New EventHandler([Of] WorkflowExecutionEventArgs)(AddressOf Me.WorkflowExecutionEvent)
            Dim trackingCallingState As TrackingCallingState = skedExec.TrackingCallingState
            Dim broker As TrackingListenerBroker = DirectCast(skedExec.RootActivity.GetValue(WorkflowExecutor.TrackingListenerBrokerProperty), TrackingListenerBroker)
            Dim target As TrackingListener = Nothing
            Dim reference As WeakReference = Nothing
            If e.Loading Then
                Dim flag As Boolean = False
                SyncLock Me._listenerLock
                    flag = Me._listeners.TryGetValue(skedExec.InstanceId, reference)
                End SyncLock
                If flag Then
                    Try 
                        target = [TryCast](reference.Target,TrackingListener)
                    Catch exception1 As InvalidOperationException
                    End Try
                End If
                If (Not target Is Nothing) Then
                    target.Broker = broker
                    goto Label_0196
                End If
                target = Me.GetTrackingListener(skedExec.WorkflowDefinition, skedExec, broker)
                If (target Is Nothing) Then
                    goto Label_0196
                End If
                If (Not reference Is Nothing) Then
                    reference.Target = target
                    goto Label_0196
                End If
                SyncLock Me._listenerLock
                    Me._listeners.Add(skedExec.ID, New WeakReference(target))
                    goto Label_0196
                End SyncLock
            End If
            target = Me.GetTrackingListener(skedExec.WorkflowDefinition, skedExec)
            If (Not target Is Nothing) Then
                skedExec.RootActivity.SetValue(WorkflowExecutor.TrackingListenerBrokerProperty, target.Broker)
                SyncLock Me._listenerLock
                    Me._listeners.Add(skedExec.ID, New WeakReference(target))
                    goto Label_0196
                End SyncLock
            End If
            skedExec.RootActivity.SetValue(WorkflowExecutor.TrackingListenerBrokerProperty, New TrackingListenerBroker)
        Label_0196:
            If (Not target Is Nothing) Then
                AddHandler skedExec.WorkflowExecutionEvent, New EventHandler([Of] WorkflowExecutionEventArgs)(AddressOf target.WorkflowExecutionEvent)
            End If
        End Sub


        ' Properties
        Friend ReadOnly Property TrackingProfileManager As TrackingProfileManager
            Get
                Return Me._profileManager
            End Get
        End Property


        ' Fields
        Private _initialized As Boolean
        Private _interval As Double = 60000
        Private _listenerLock As Object ModReq(IsVolatile) = New Object
        Private _listeners As Dictionary([Of] Guid, WeakReference) = New Dictionary([Of] Guid, WeakReference)
        Private _profileManager As TrackingProfileManager = New TrackingProfileManager
        Private _services As List([Of] TrackingService)
        Private _timer As Timer
    End Class
End Namespace

